/* 算法实现模板 */
/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import {mat_slove} from "../../../common/SVMFuncs.js";

import React from "react";
import ReactDom from 'react-dom';
import { loadDataset, txtToMatrix , mdRender} from "../../../common/Common";
import MatView2D, {createRandomPointStyleDict} from "../../../visual/MatView2D";
import Mat from "../../../common/Mat";

import 'katex/dist/katex.min.css';
import TDom from "../../../common/TDom";
import TCommon from "../../../common/TCommon";
import TMatrix from "../../../common/TMatrix";
import { showMathStringInTable } from "../../../visual/Table2D";


class LinearSepSVMMaxMarginMethod extends AlgorithmBase{
  constructor(){
    super('LinearSepSVMMaxMarginMethod');

    this.descriptionUrl = `${this.homepage}/data/slm/ch7/7.1/README.md`;
    this.datasetUrl     = `${this.homepage}/data/slm/ch7/7.1/dataset.txt`;
    this.x = [];
    this.y = [];
    this.svm_s=[]
  }

  init(){ 
    this.reset();

    this.initTrainDS();
  }

  reset(){
    this.dataset = undefined;
    this.trainDS = undefined;
    this.model   = {toler:10e-3, C:0.6}

    this.clear();

    this.initDescription();
    this.updateModel();
  }
  
  // 模型UI

  // 渲染模型到页面
  updateModel(w, b){

    if(w === undefined) w = new Mat.zeros([1, 2]);
    if(b === undefined) b = 0;

    // 显示数据集
    if(this.trainDS){
      ReactDom.unmountComponentAtNode(this.inputDiv);
      ReactDom.render(
        <React.StrictMode>
          <MatView2D 
            Mat     = {new TMatrix.Mat(this.trainDS.Mat)}
            Labels  = {this.trainDS.Labels}
            XRange  = {[0, 10]}
            YRange  = {[0, 10]}
            Separator = { {type:'line', w:w.toVector(), b:b} }
            PointStyleDict = {this.pointStyleDict}
          />
        </React.StrictMode>,
        this.inputDiv
      );
    }
  }

  // 输入数据UI
  initInputUI(){

    // 文件选择器
    const fileSelector      = document.createElement('input');
    fileSelector.type       = 'file';
    fileSelector.hidden     = true;
    fileSelector.onchange   = this.handleFileChange.bind(this);   
    
    // this.addButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    TDom.createButton(this.domElementList.input, "获取服务器数据", this.initTrainDS.bind(this));
    this.addButton(this.domElementList.input, "使用本地数据", this.openFileSelector.bind(this));
    this.domElementList.input.appendChild(fileSelector);
    this.addButton(this.domElementList.input, "训练", this.train.bind(this));

    this.labelTrainTips = this.addLabel(this.domElementList.input, '');
    this.inputDiv       = this.addDiv(this.domElementList.input);

    this.fileSelector  = fileSelector;
  }

  // 输出UI
  initOutputUI(){
    this.outputDiv   = this.addDiv(this.domElementList.output);
    this.trainLogDiv = this.addDiv(this.domElementList.output);
  }

  // 获取描述md文件
  initDescription(){
    TDom.getDataFromUrl(this.descriptionUrl, (txt)=>{
      mdRender(txt, this.descriptionDiv);
    })

  }

  // 获取服务器数据集
  initTrainDS(){
    this.prepare(this.datasetUrl);
  }

  // 准备数据训练数据
  prepare(url){
    TDom.getDataFromUrl(url, 
      (txt)=>{
      this.dataset = txtToMatrix(txt);
      this.trainDS = TCommon.loadDataset(this.dataset);
      this.pointStyleDict = createRandomPointStyleDict(this.trainDS.Labels);
      this.updateModel();
    })
  }

  openFileSelector(){
    this.fileSelector.click();
  }
  // 获取文件路径加载
  handleFileChange(event){
    const files = event.target.files;

    for(let i = 0, il = files.length; i < il; i++){
      let reader = new FileReader();
      reader.onload = (e) => {      
        this.prepare(e.target.result);
        if(i === files.length - 1){
          event.target.value = '';
        }
      };
      reader.readAsDataURL(files[i])
    }
  }

  svm_mat(x=this.x,y=this.y){
    var svm = [
      [1,0,0],
      [0,1,0],
      [0,0,0]
    ]
    for(var i=0;i<y.length;i++){
      svm[0].push(-x[i][0]*y[i])
      svm[1].push(-x[i][1]*y[i])
      svm[2].push(-y[i])
      svm.push([y[i]*x[i][0], y[i]*x[i][1], y[i]])
      for(var j=0;j<y.length;j++){
        svm[svm.length-1].push(0)
      }
    }
    svm[0].push(0)
    svm[1].push(0)
    svm[2].push(0)
    for(var k=0;k<y.length;k++){
      svm[3+k].push(1)
    }

    return svm
  }

  alpha_kkt(s_mat=[]){
    var kkt = 1
    for(var i=3;i<s_mat.length;i++){
      if(!(s_mat[i] > 0)){
        kkt = 0
        break
      }
    }

    return kkt
  }

  svm_slove(x=this.x,y=this.y){
    this.svm_s=[]
    this.slove = 0
    return this.svm_slove_(x,y)
  }

  svm_slove_(x=this.x,y=this.y){
    if(this.slove==0){
      var x_log = []
      for(var j=0;j<x.length;j++){
        x_log.push([`[${x[j]}]`])
      }  

      var svm = this.svm_mat(x,y)
      var s = mat_slove(svm)
      
      var kkt = 0
      if(s != undefined){
        kkt = this.alpha_kkt(s)
      }

      if(s == undefined || kkt==0){
        this.count++
        this.trainLog.push([`${this.count}`,`${x_log}`,`---`,`---`,`无解`])

        var x_pos=[],x_neg=[],y_pos=[],y_neg=[]

        for(var i=0;i<y.length;i++){
          if(y[i]==1){
            x_pos.push(x[i])
            y_pos.push(y[i])
          }
          else{
            x_neg.push(x[i])
            y_neg.push(y[i])
          }
        }

        if(y_pos.length>1 && y_neg.length>1){
          for(var j=0;j<y.length;j++){
            var x_ = []
            var y_ = []
            for(var k=0;k<y.length;k++){
              if(k!=j){
                x_.push(x[k])
                y_.push(y[k])
              } 
            }
            this.svm_slove_(x_,y_)
          }
        }
        else if(y_pos.length>1){
          for(var j=0;j<y_pos.length;j++){
            var x_ = []
            var y_ = []
            for(var k=0;k<y_pos.length;k++){
              if(k!=j){
                x_.push(x_pos[k])
                y_.push(y_pos[k])
              } 
            }
            x_.push(x_neg[0])
            y_.push(y_neg[0])
            this.svm_slove_(x_,y_)
          }
        }
        else if(y_neg.length>1){
          for(var j=0;j<y_neg.length;j++){
            var x_ = []
            var y_ = []
            for(var k=0;k<y_neg.length;k++){
              if(k!=j){
                x_.push(x_neg[k])
                y_.push(y_neg[k])
              } 
            }
            x_.push(x_pos[0])
            y_.push(y_pos[0])
            this.svm_slove_(x_,y_)
          }
        }
      }
      else{
        var flag = 0
        this.count++
        s[0]=parseFloat(s[0].toFixed(2))
        s[1]=parseFloat(s[1].toFixed(2))
        s[2]=parseFloat(s[2].toFixed(2))
        for(var i=0;i<this.y.length;i++){
          if(parseFloat((this.y[i]*(s[0]*this.x[i][0] + s[1]*this.x[i][1] + s[2]) -1).toFixed())<-0.001){
            flag = 1
            break
          }
        }

        if(flag!=1){
          this.svm_s = s;
          var w = parseFloat((Math.pow(s[0],2)+Math.pow(s[1],2)).toFixed(2))
          if(w<this.min){
            this.min = w
            this.slove = 1
            this.trainLog.push([`${this.count}`,`${x_log}`,`${s[0].toFixed(2)}`+' x_1 ' + '+' + `${s[1].toFixed(2)}` + ' x_2 ' + '+' + `${s[2].toFixed(2)}`+ '= 0',`${w}`,`局部最优解！`])
          }
          else{
            this.trainLog.push([`${this.count}`,`${x_log}`,`${s[0].toFixed(2)}`+' x_1 ' + '+' + `${s[1].toFixed(2)}` + ' x_2 ' + '+' + `${s[2].toFixed(2)}`+ '= 0',`${w}`,`非最优！`])
          }
        }
        else{
          this.trainLog.push([`${this.count}`,`${x_log}`,`${s[0].toFixed(2)}`+' x_1 ' + '+' + `${s[1].toFixed(2)}` + ' x_2 ' + '+' + `${s[2].toFixed(2)}`+ '= 0','---',`非最优！`])
        }       
      }
    }
    return this.svm_s
  }

  // 训练
  train(){
    const trainDS = this.trainDS;
    if(!trainDS) return;

    let X = new Mat.Mat(trainDS.Mat);
    let y = new Mat.Mat(trainDS.Labels);
    var w = new Mat.zeros([1, 2]);
    var b = 0.0;

    for(var i=0;i<y.length;i++){
      y[i]=y[i][0]
    }

    var x=[]
    for(var i=0;i<X.length;i++){
      x.push(X[i])
    }

    this.x = x
    this.y = y

    this.trainLog = []
    this.count = 0
    this.trainLog.push(["迭代次数","分类点","分离超平面","目标函数值","是否最优"])
    this.min = Infinity
    var slove = this.svm_slove(x,y)
    w[0][0] = slove[0];
    w[0][1] = slove[1];
    b = slove[2]


    this.updateModel(w, b);
    this.labelTrainTips.innerText = '训练完毕';
    if(this.trainLogDiv) this.trainLogDiv.innerHTML = '';
    showMathStringInTable(this.trainLogDiv, this.trainLog);
                
  }
}


export default LinearSepSVMMaxMarginMethod;